package com.cli.springboot_rabbitmq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author xiongmin
 * @Description
 * @Date 2021/2/25 18:31
 * @Version 1.0
 **/
@Configuration
public class RabbitMQConfig {

    @Value("${spring.rabbitmq.addresses}")
    private String addresses;

    @Value("${spring.rabbitmq.virtual-host}")
    private String virtualHost;

    @Value("${spring.rabbitmq.username}")
    private String username;

    @Value("${spring.rabbitmq.password}")
    private String password;

    @Value("${spring.rabbitmq.default-exchange}")
    private String defaultExchange;

    @Autowired
    private ConfirmCallbackService confirmCallbackService;
    @Autowired
    private ReturnCallbackService returnCallbackService;


    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setAddresses(addresses);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualHost);
//        connectionFactory.setPublisherConfirms(rabbitmqProps.isPublisherConfirms()); //消息回调，必须要设置
        return connectionFactory;
    }

    /**
     * 使用的自己的创建的RabbitMQ 完全没有使用到RabbitMQ相关的默认配置，即使在yaml文件中配置了消费者手动确认，使用如下的rabbitMQ 也是无效的，
     * 因为RabbitTemplate相关的ConnectionFactory 没有设置消费者手动确认消息，这里不会使用到yaml的配置
     * @return
     */
    @Bean(value = "rabbitTemplateMessaging")
    public RabbitTemplate rabbitTemplateMessaging() {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
        // 消息类型转换器 -- 数据转换为json存入消息队列
        rabbitTemplate.setMessageConverter(jackson2MessageConverter());
        // 设置默认的交换机，如果发送的消息没有指定交换机，则使用默认的交换机
        rabbitTemplate.setExchange(defaultExchange);
        /**
         * mandatory：交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
         * true：RabbitMQ会调用Basic.Return命令将消息返回给生产者
         * false：RabbitMQ会把消息直接丢弃
         */
        rabbitTemplate.setMandatory(true);
        /**
         * 消费者确认收到消息后，手动ack回执回调处理
         */
        rabbitTemplate.setConfirmCallback(confirmCallbackService);
        /**
         * 消息投递到队列失败回调处理
         */
        rabbitTemplate.setReturnsCallback(returnCallbackService);


        // 服务端响应发送到的队列 reply-address 格式: exchange/routingKey
        rabbitTemplate.setReplyAddress("messaging/messaging-response");
        // 设置回复和接受消息的时间，单位为毫秒
        rabbitTemplate.setReplyTimeout(20000);
        rabbitTemplate.setReceiveTimeout(20000);
        return rabbitTemplate;
    }

    /**
     * @param connectionFactory connectionFactory属性信息会直接是用yaml配置文件中配置的
     * @return
     */
    @Bean(value = "rabbitTemplateInit")
    public RabbitTemplate rabbitTemplateInit(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        // 消息类型转换器 -- 数据转换为json存入消息队列
        rabbitTemplate.setMessageConverter(jackson2MessageConverter());
        // 设置默认的交换机，如果发送的消息没有指定交换机，则使用默认的交换机
        rabbitTemplate.setExchange(defaultExchange);
        /**
         * mandatory：交换器无法根据自身类型和路由键找到一个符合条件的队列时的处理方式
         * true：RabbitMQ会调用Basic.Return命令将消息返回给生产者
         * false：RabbitMQ会把消息直接丢弃
         */
        rabbitTemplate.setMandatory(true);
        /**
         * 消费者确认收到消息后，手动ack回执回调处理
         */
        rabbitTemplate.setConfirmCallback(confirmCallbackService);
        /**
         * 消息投递到队列失败回调处理
         */
        rabbitTemplate.setReturnsCallback(returnCallbackService);


        // 服务端响应发送到的队列 reply-address 格式: exchange/routingKey
        rabbitTemplate.setReplyAddress("messaging/messaging-response");
        // 设置回复和接受消息的时间，单位为毫秒
        rabbitTemplate.setReplyTimeout(20000);
        rabbitTemplate.setReceiveTimeout(20000);
        return rabbitTemplate;
    }

    /**
     * 的作用解释如下：
     * 数据转换为json存入消息队列
     * [[RabbitMQ]Jackson2JsonMessageConverter转换实体类常的问题](https://blog.csdn.net/qq_31897023/article/details/103875594)
     * [Springboot Rabbitmq 使用Jackson2JsonMessageConverter 消息传递后转对象](https://www.cnblogs.com/timseng/p/11688019.html)
     * @return
     */
    @Bean
    public Jackson2JsonMessageConverter jackson2MessageConverter() {
        Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter();
        return converter;
    }


    /**
     * 创建topic模式的交换器
     * @return
     */
    @Bean
    TopicExchange exchange() {
        return new TopicExchange("topicExchange",true,false);
    }

    /**
     * 创建fanout模式的交换器
     * 发布订阅模式
     * 发布订阅是交换机针对队列来说的，一个消息可投入一个或多个队列
     * 注意：多个消费者可以订阅同一个队列，这时队列中的消息会被平均分摊（Round-Robin，即轮询）给多个消费者进行处理，而不是每个消费者都收到所有的消息并处理，这样避免的消息被重复消费。
     * @return
     */
    @Bean
    FanoutExchange fanoutExchange() {
        // durable:是否持久化,默认是false,持久化队列：会被存储在磁盘上，当消息代理重启时仍然存在，暂存队列：当前连接有效; 如果持久性，则RabbitMQ重启后，交换机还存在
        // autoDelete:是否自动删除，当所有与之绑定的消息队列都完成了对此交换机的使用后，删掉它
        return new FanoutExchange("fanoutExchange",true,false);
    }

    //Direct交换机 起名：TestDirectExchange
    @Bean
    DirectExchange TestDirectExchange() {
        // durable:是否持久化,默认是false,持久化队列：会被存储在磁盘上，当消息代理重启时仍然存在，暂存队列：当前连接有效
        // autoDelete:是否自动删除，当没有生产者或者消费者使用此队列，该队列会自动删除。
        return new DirectExchange("TestDirectExchange",true,false);
    }

    //队列 起名：TestDirectQueue
    @Bean
    public Queue TestDirectQueue() {
        // durable:是否持久化,默认是false,持久化队列：会被存储在磁盘上，当消息代理重启时仍然存在，暂存队列：当前连接有效
        // exclusive:默认也是false，只能被当前创建的连接使用，而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除，当没有生产者或者消费者使用此队列，该队列会自动删除。
        //   return new Queue("TestDirectQueue",true,true,false);

        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue("TestDirectQueue",true);
    }

    //绑定  将队列和交换机绑定, 并设置用于匹配键：TestDirectRouting
    @Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
    }


    // 默认的 Direct交换机 起名：DefaultDirectExchange
    @Bean
    DirectExchange DefaultDirectExchange() {
        // durable:是否持久化,默认是false,持久化队列：会被存储在磁盘上，当消息代理重启时仍然存在，暂存队列：当前连接有效
        // autoDelete:是否自动删除，当没有生产者或者消费者使用此队列，该队列会自动删除。
        return new DirectExchange(defaultExchange,true,false);
    }

    //队列 起名：DefaultDirectQueue
    @Bean
    public Queue DefaultDirectQueue() {
        // durable:是否持久化,默认是false,持久化队列：会被存储在磁盘上，当消息代理重启时仍然存在，暂存队列：当前连接有效
        // exclusive:默认也是false，只能被当前创建的连接使用，而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除，当没有生产者或者消费者使用此队列，该队列会自动删除。
        //   return new Queue("TestDirectQueue",true,true,false);

        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue("DefaultDirectQueue",true);
    }

    //绑定  将队列和交换机绑定, 并设置用于匹配键：DefaultDirectRouting
    @Bean
    Binding bindingDefaultDirect() {
        return BindingBuilder.bind(DefaultDirectQueue()).to(DefaultDirectExchange()).with("DefaultDirectRouting");
    }


    /**
     *  创建三个队列 ：fanout.A   fanout.B  fanout.C
     *  将三个队列都绑定在交换机 fanoutExchange 上
     *  因为是扇型交换机, 路由键无需配置,配置也不起作用
     */
    @Bean
    public Queue queueA() {
        return new Queue("fanout.A");
    }

    @Bean
    public Queue queueB() {
        return new Queue("fanout.B");
    }

    @Bean
    public Queue queueC() {
        return new Queue("fanout.C");
    }

    @Bean
    Binding bindingExchangeA() {
        return BindingBuilder.bind(queueA()).to(fanoutExchange());
    }

    @Bean
    Binding bindingExchangeB() {
        return BindingBuilder.bind(queueB()).to(fanoutExchange());
    }

    @Bean
    Binding bindingExchangeC() {
        return BindingBuilder.bind(queueC()).to(fanoutExchange());
    }



    //绑定键
    public final static String MAN = "topic.MAN";
    public final static String WOMAN = "topic.WOMAN";

    @Bean
    public Queue firstQueue() {
        return new Queue(RabbitMQConfig.MAN);
    }

    @Bean
    public Queue secondQueue() {
        return new Queue(RabbitMQConfig.WOMAN);
    }



    //将firstQueue和topicExchange绑定,而且绑定的键值为topic.MAN
    //这样只要是消息携带的路由键是topic.MAN,才会分发到该队列
    @Bean
    Binding bindingExchangeMessage() {
        return BindingBuilder.bind(firstQueue()).to(exchange()).with(MAN);
    }

    //将secondQueue和topicExchange绑定,而且绑定的键值为用上通配路由键规则topic.#
    // 这样只要是消息携带的路由键是以topic.开头,都会分发到该队列
    @Bean
    Binding bindingExchangeMessage2() {
        return BindingBuilder.bind(secondQueue()).to(exchange()).with("topic.#");
    }



}
